using System.Collections.Generic;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework;
using SolarWinds.Particles;

namespace SolarWinds.Shapes
{
    public class ParticleEmitterShape : Shape
    {
        #region Fields
        
        public const int cMaxEmitterParticles = 10000;

        // Your vertex und pixel shader
        private Effect mParticleEffect;
        private Texture2D mColorRamp;
        private Texture2D mTexture;

        // Effect parameters
        private EffectParameter mWorldParam;
        private EffectParameter mViewParam;
        private EffectParameter mProjectionParam;
        private EffectParameter mCameraPositionParam;
        private EffectParameter mCameraUpParam;
        private EffectParameter mTextureParam;
        private EffectParameter mColorRampParam;

        // vertex buffer
        private VertexBuffer mVertexBuffer;
        private VertexDeclaration mVertexDecl;

        // index buffer
        private IndexBuffer mIndexBuffer;

        // vertex and index data arrays
        private ParticleVertex[] mVertexData;
        private int[] mIndexData;

        // sprite vertices
        private static readonly Vector3 sV0 = -Vector3.UnitX * 0.5f + Vector3.UnitY * 0.5f;
        private static readonly Vector3 sV1 = Vector3.UnitX * 0.5f + Vector3.UnitY * 0.5f;
        private static readonly Vector3 sV2 = Vector3.UnitX * 0.5f - Vector3.UnitY * 0.5f;
        private static readonly Vector3 sV3 = -Vector3.UnitX * 0.5f - Vector3.UnitY * 0.5f;

        #endregion

        #region Particle Vertex Element Struct

        private struct ParticleVertex
        {
            private Vector3 mVPos;
            private Vector3 mPPos;
            private Vector4 mCol;
            private Vector2 mTex;
            private Vector2 mSize;
            private float   mTwist;
            private float   mLife;
            
            public void Set(Vector3 vPos, 
                            Vector3 pPos,
                            Vector4 col,
                            Vector2 tex,
                            Vector2 size,
                            float   twist,
                            float   life)
            {
                mVPos = vPos;
                mPPos = pPos;
                mCol = col;
                mTex = tex;
                mSize = size;
                mTwist = twist;
                mLife = life;
            }
            
            public static readonly VertexElement[] sElements =
                {
                    new VertexElement(0, 0,  VertexElementFormat.Vector3, VertexElementMethod.Default, VertexElementUsage.Position, 0),          //mVPos
                    new VertexElement(0, 12, VertexElementFormat.Vector3, VertexElementMethod.Default, VertexElementUsage.Normal, 0),            //mPPos
                    new VertexElement(0, 24, VertexElementFormat.Vector4, VertexElementMethod.Default, VertexElementUsage.Color, 0),             //mCol
                    new VertexElement(0, 40, VertexElementFormat.Vector2, VertexElementMethod.Default, VertexElementUsage.TextureCoordinate, 0), //mTex
                    new VertexElement(0, 48, VertexElementFormat.Vector2, VertexElementMethod.Default, VertexElementUsage.TextureCoordinate, 1), //mSize
                    new VertexElement(0, 56, VertexElementFormat.Single,  VertexElementMethod.Default, VertexElementUsage.TextureCoordinate, 2), //mTwist
                    new VertexElement(0, 60, VertexElementFormat.Single,  VertexElementMethod.Default, VertexElementUsage.TextureCoordinate, 3)  //mLife
                };

            public const int Size = 12 + 12 + 16 + 8 + 8 + 4 + 4;
        }

        #endregion
        
        #region Properties;

        // Reference to particle emitter
        ParticleEmitter mEmitter;
        private List<Vector3> mParticlePositions;

        public ParticleEmitter Emitter
        {
            get { return mEmitter; }
            set { mEmitter = value; }
        }

        #endregion

        public ParticleEmitterShape (Game game)
            : base(game)
        {
            BoundingSphereVisible = true;
            BoundingBoxVisible = true;
        }

        #region Drawing

        public override void  Draw(GameTime gameTime)
        {
 	        base.Draw(gameTime);

            GraphicsDevice device = Game.GraphicsDevice;

            // Set common effect parameters ... 
            // commit changes later, when texture 
            // of emitter is known
            mParticleEffect.CurrentTechnique = mParticleEffect.Techniques[0];
            mWorldParam.SetValue(World);
            mViewParam.SetValue(View);
            mProjectionParam.SetValue(Projection);
            mCameraPositionParam.SetValue(View.Translation);
            mCameraUpParam.SetValue(View.Up);
            
            // render emitter node
            {
                // set effect parmaters and commit them
                mColorRampParam.SetValue(mColorRamp);
                mTextureParam.SetValue(mTexture);
                mParticleEffect.CommitChanges();

                // get vertex info of each particle from emitter
                int iVertex = 0;
                int iIndex = 0;
                for (int pi = 0; pi < mEmitter.ActiveParticleCount; ++pi)
                {
                    Particle p = mEmitter.Particles[pi];

                    // set index data
                    mIndexData[iIndex++] = iVertex;
                    mIndexData[iIndex++] = iVertex + 1;
                    mIndexData[iIndex++] = iVertex + 2;

                    mIndexData[iIndex++] = iVertex;
                    mIndexData[iIndex++] = iVertex + 2;
                    mIndexData[iIndex++] = iVertex + 3;

                    // set vertex data
                    mVertexData[iVertex++].Set(sV0, p.Position, p.Color.ToVector4(), new Vector2(0.0f, 0.0f), p.Size, p.Twist, p.Life);
                    mVertexData[iVertex++].Set(sV1, p.Position, p.Color.ToVector4(), new Vector2(1.0f, 0.0f), p.Size, p.Twist, p.Life);
                    mVertexData[iVertex++].Set(sV2, p.Position, p.Color.ToVector4(), new Vector2(1.0f, 1.0f), p.Size, p.Twist, p.Life);
                    mVertexData[iVertex++].Set(sV3, p.Position, p.Color.ToVector4(), new Vector2(0.0f, 1.0f), p.Size, p.Twist, p.Life);
                }

                if (iVertex > 0)
                {
                    // Set the vertex buffer data to the array of vertices
                    mVertexBuffer.SetData(mVertexData, 0, iVertex);
                    mIndexBuffer.SetData(mIndexData, 0, iIndex);

                    device.VertexDeclaration = mVertexDecl;
                    device.Vertices[0].SetSource(mVertexBuffer, 0, ParticleVertex.Size);
                    device.Indices = mIndexBuffer;

                    mParticleEffect.Begin(SaveStateMode.SaveState);
                    foreach (EffectPass pass in mParticleEffect.Techniques["ParticleTech"].Passes)
                    {
                        pass.Begin();
                        device.DrawIndexedPrimitives(PrimitiveType.TriangleList, 0, 0, iIndex, 0, iVertex / 2);
                        pass.End();
                    }
                    mParticleEffect.End();
                }
            }
        }

        public override void Update(GameTime gameTime)
        {
            base.Update(gameTime);
        
            mEmitter.Update(gameTime);

            var min = new Vector3(float.MaxValue);
            var max = new Vector3(float.MinValue);
        
            mParticlePositions.Clear();
            for (int pi = 0; pi < mEmitter.ActiveParticleCount; ++pi)
            {
                Vector3 position = mEmitter.Particles[pi].Position;
                mParticlePositions.Add(position);

                if (position.X < min.X) min.X = position.X;
                if (position.Y < min.Y) min.Y = position.Y;
                if (position.Z < min.Z) min.Z = position.Z;

                if (position.X > max.X) max.X = position.X;
                if (position.Y > max.Y) max.Y = position.Y;
                if (position.Z > max.Z) max.Z = position.Z;
            }

            if (mParticlePositions.Count > 0)
            {
                mBoundingSphere = BoundingSphere.CreateFromPoints(mParticlePositions);
                mBoundingBox = new BoundingBox(min, max);
            }
        }

        protected override BoundingSphere UpdateBoundingSphere()
        {
            return new BoundingSphere();
        }

        protected override BoundingBox UpdateBoundingBox()
        {
            return new BoundingBox();
        }

        #endregion

        #region IDeviceDependable Members derived from Spatial

        protected override void LoadContent()
        {
            GraphicsDevice device = Game.GraphicsDevice;

            // Load and compile the effect
            mParticleEffect = Game.Content.Load<Effect>("Shaders/SimpleParticle");
            mColorRamp = Game.Content.Load<Texture2D>(@"Textures\" + mEmitter.Description.ColorRampAsset);
            mTexture = Game.Content.Load<Texture2D>(@"Textures\" + mEmitter.Description.TextureAsset);

            // Bind the effect params
            mWorldParam = mParticleEffect.Parameters["matWorld"];
            mViewParam = mParticleEffect.Parameters["matView"];
            mProjectionParam = mParticleEffect.Parameters["matProjection"];
            mCameraPositionParam = mParticleEffect.Parameters["cameraPosition"];
            mCameraUpParam = mParticleEffect.Parameters["cameraUp"];
            mTextureParam = mParticleEffect.Parameters["particleTexture"];
            mColorRampParam = mParticleEffect.Parameters["colorRamp"];

            // create vertex buffer for all particles
            mVertexBuffer = new VertexBuffer(device, ParticleVertex.Size * 4 * mEmitter.Particles.Length, BufferUsage.WriteOnly);
            mIndexBuffer = new IndexBuffer(device, typeof(int), 6 * mEmitter.Particles.Length, BufferUsage.WriteOnly);

            mVertexDecl = new VertexDeclaration(device, ParticleVertex.sElements);

            // create array for VertexElements
            mVertexData = new ParticleVertex[cMaxEmitterParticles * 4];
            mIndexData = new int[cMaxEmitterParticles * 6];

            mParticlePositions = new List<Vector3>(mEmitter.Particles.Length);

        }

        protected override void UnloadContent()
        {
            // free all vertex buffers and delete them
            if (mVertexBuffer != null)
            {
                mVertexBuffer.Dispose();
                mVertexBuffer = null;
            }

            // free all index buffers and delete them
            if (mIndexBuffer != null)
            {
                mIndexBuffer.Dispose();
                mIndexBuffer = null;
            }

            // free all vertex declaration and delete it
            if (mVertexDecl != null)
            {
                mVertexDecl.Dispose();
                mVertexDecl = null;
            }
        }
    
        #endregion
    }
}